/*!--------------------------------------------------------------------------
 @file simat.c
 @date 11/18/2010

 See header file for description.

*/
/*

 Copyright (c) 2010, XInfo
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 
    * Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
    * Neither the name of the XInfo nor the names of its contributors may be
    used to endorse or promote products derived from this software without
    specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------*/

#include "xutility.h"
#include "siconst.h"
#include "simat.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

/*! Matrix definition

 @todo: Only two dimensions matrix is supported now. Need multiple dimensions?

*/
struct _SIMat
{
    void* m_data;                               /*!< Data */
    int m_rows;                                 /*!< Number of rows */
    int m_columns;                              /*!< Number of columns */
    int m_flags;                                /*!< Depth and channels */
};


/*! Recreate data of matrix.

 @todo: Matrix conversion isn't supported now.

 @return X Error
*/
static int
simat_recreate_data(SIMat* mat                  /*!< Matrix instance */
                    );


#pragma mark PublicMethods

/* See "simat.h" for description. */
int
simat_create(int rows, int columns, int type, SIMat** mat)
{
    if(rows <= 0 || columns <= 0 || NULL == mat)
    {
        RES_ERR(X_INVAL, "");
    }

    // Create instance.
    int length = sizeof(SIMat);
    SIMat* newMat = (SIMat*)malloc(length);
    if(NULL == newMat)
    {
        RES_ERR(X_NOMEM, "");
    }
    memset(newMat, 0, length);

    // Set members.
    newMat->m_rows = rows;
    newMat->m_columns = columns;
    newMat->m_flags = SI_GET_MATRIX_TYPE(type);

    // Create data of matrix.
    int err = simat_recreate_data(newMat);
    if(err != 0)
    {
        simat_destroy(newMat);
        newMat = NULL;
        RES_ERR(err, "");
    }

    *mat = newMat;

    return 0;
}

/* See "simat.h" for description. */
int
simat_create_by_copy(SIMat* copyMat, SIMat** mat)
{
    if(NULL == copyMat || NULL == mat)
    {
        RES_ERR(X_INVAL, "");
    }

    int err = simat_create(copyMat->m_rows, copyMat->m_columns, copyMat->m_flags, mat);
    if(err != 0)
    {
        RES_ERR(err, "");
    }
    assert(*mat != NULL);

    simat_copy(*mat, copyMat);

    return 0;
}

/* See "simat.h" for description. */
void
simat_destroy(SIMat* mat)
{
    if(mat != NULL)
    {
        if(mat->m_data != NULL)
        {
            free(mat->m_data);
            mat->m_data = NULL;
        }
        free(mat);
        mat = NULL;
    }
}

/* See "simat.h" for description. */
int
simat_get_data(SIMat* mat, void** data)
{
    if(NULL == mat || NULL == data)
    {
        RES_ERR(X_INVAL, "");
    }

    *data = mat->m_data;
    return 0;
}

/* See "simat.h" for description. */
int
simat_get_rows(SIMat* mat, int* rows)
{
    if(NULL == mat || NULL == rows)
    {
        RES_ERR(X_INVAL, "");
    }

    *rows = mat->m_rows;
    return 0;
}

/* See "simat.h" for description. */
int
simat_get_columns(SIMat* mat, int* columns)
{
    if(NULL == mat || NULL == columns)
    {
        RES_ERR(X_INVAL, "");
    }

    *columns = mat->m_columns;
    return 0;
}

/* See "simat.h" for description. */
int
simat_get_type(SIMat* mat, int* type)
{
    if(NULL == mat || NULL == type)
    {
        RES_ERR(X_INVAL, "");
    }

    *type = SI_GET_MATRIX_TYPE(mat->m_flags);
    return 0;
}

/* See "simat.h" for description. */
int
simat_get_depth(SIMat* mat, int* depth)
{
    if(NULL == mat || NULL == depth)
    {
        RES_ERR(X_INVAL, "");
    }

    *depth = SI_GET_DEPTH(mat->m_flags);
    return 0;
}

/* See "simat.h" for description. */
int
simat_get_channels(SIMat* mat, int* channels)
{
    if(NULL == mat || NULL == channels)
    {
        RES_ERR(X_INVAL, "");
    }

    *channels == SI_GET_CHANNELS(mat->m_flags);
    return 0;
}

/* See "simat.h" for description. */
int
simat_get_size(SIMat* mat, int* size)
{
    if(NULL == mat || NULL == size)
    {
        RES_ERR(X_INVAL, "");
    }

    *size = mat->m_rows * mat->m_columns;
    return 0;
}

/* See "simat.h" for description. */
int
simat_is_empty(SIMat* mat)
{
    if(NULL == mat)
    {
        DEBUG("Argument \"mat\" is NULL.");
        return TRUE;
    }
    return (NULL == mat->m_data || mat->m_rows <= 0 || mat->m_columns <= 0);
}

/* See "simat.h" for description. */
int
simat_is_same_type(SIMat* mat, SIMat* rhs)
{
    if(NULL == mat && NULL == rhs)
    {
        DEBUG("Argument \"mat\" and \"rhs\" are both NULL.");
        return TRUE;
    }

    if(NULL == mat || NULL == rhs)
    {
        DEBUG("Argument \"mat\" or \"rhs\" is NULL.");
        return FALSE;
    }

    return (mat->m_rows == rhs->m_rows &&
            mat->m_columns == rhs->m_columns &&
            mat->m_flags == rhs->m_flags);
}

/* See "simat.h" for description. */
void
simat_clear(SIMat* mat)
{
    if(NULL == mat)
    {
        RES("");
    }
    if(mat->m_data != NULL)
    {
        free(mat->m_data);
        mat->m_data = NULL;
    }
    mat->m_rows = 0;
    mat->m_columns = 0;
    mat->m_flags = 0;
}

/* See "simat.h" for description. */
int
simat_reset(SIMat* mat, int rows, int columns, int type)
{
    if(NULL == mat || rows <= 0 || columns <= 0)
    {
        RES_ERR(X_INVAL, "");
    }

    // Set type.
    int flags = SI_GET_MATRIX_TYPE(type);
    if(mat->m_rows != rows ||
       mat->m_columns != columns ||
       mat->m_flags != flags)
    {
        simat_clear(mat);
        mat->m_rows = rows;
        mat->m_columns = columns;
        mat->m_flags = flags;
    }

    // Set data.
    int err = 0;
    if(NULL == mat->m_data)
    {
        err = simat_recreate_data(mat);
    }
    else
    {
        int size = 0;
        err = simat_get_size(mat, &size);
        if(0 == err)
        {
            int length = SI_GET_DEPTH_SIZE(mat->m_flags) * size;
            assert(length > 0);
            memset(mat->m_data, 0, length);            
        }
    }

    RES_ERR(err, "");
}

/* See "simat.h" for description. */
int
simat_copy(SIMat* dst, SIMat* src)
{
    if(NULL == dst || NULL == src)
    {
        RES_ERR(X_INVAL, "");
    }

    // Set members of destination matrix.
    simat_clear(dst);
    dst->m_rows = src->m_rows;
    dst->m_columns = src->m_columns;
    dst->m_flags = src->m_flags;

    // Get size of source matrix.
    int size = 0;
    if(simat_get_size(src, &size) <= 0)
    {
        DEBUG("Size of matrix \"src\" is 0.");
        return 0;
    }
    int length = SI_GET_DEPTH_SIZE(src->m_flags) * size;

    // Set data of destination matrix.
    if(NULL == src->m_data)
    {
        DEBUG("Data of matrix \"src\" is 0.");
        return 0;
    }
    memcpy(dst->m_data, src->m_data, length);

    return 0;
}


#pragma mark PrivateMethods

/* See declaration for description. */
int
simat_recreate_data(SIMat* mat)
{
    if(NULL == mat)
    {
        RES_ERR(X_INVAL, "");
    }

    // Free data.
    if(mat->m_data != NULL)
    {
        free(mat->m_data);
        mat->m_data = NULL;
    }

    // Get size.
    int size = 0;
    if(simat_get_size(mat, &size) <= 0)
    {
        DEBUG("Size of matrix \"mat\" is 0.");
        return 0;
    }

    // Set data.
    int length = SI_GET_DEPTH_SIZE(mat->m_flags) * size;
    void* data = malloc(length);
    if(NULL == data)
    {
        RES_ERR(X_NOMEM, "");
    }
    memset(data, 0, length);
    mat->m_data = data;

    return 0;
}
